package design.donkey.auto.ddl.dialect;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

import design.donkey.auto.ddl.exception.AutoDdlException;
import design.donkey.framework.common.util.CommonUtils;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.Statements;
import net.sf.jsqlparser.statement.create.index.CreateIndex;
import net.sf.jsqlparser.statement.create.table.CreateTable;

/**
 * AbstractDialect
 *
 * @author zhangjiakung
 * @since 0.0.1
 */
@Slf4j
public abstract class AbstractDialect implements Dialect {

    /**
     * 关键字容器
     */
    protected final Map<String, Boolean> keywordHub = new HashMap<>();

    /**
     * 关键字处理字符
     */
    protected final String identifier;

    /**
     * 关键字处理字符
     * 
     * @param identifier 关键字处理字符
     */
    protected AbstractDialect(String identifier) {
        this.identifier = identifier;
        initKeyword();
    }

    @Override
    public Map<String, List<String>> maintenance(String sourceSchemaSql, String targetSchemaSql,
        Set<DdlOperation> operations) {
        // 参数检查
        if (CommonUtils.isEmpty(operations)) {
            log.warn("数据库 DDL 操作性不能为空！");
            return Collections.emptyMap();
        }
        // 解析 schema sql
        List<Statement> sourceSchemaSqlStatements = parseStatements(sourceSchemaSql);
        List<Statement> targetSchemaSqlStatements = parseStatements(targetSchemaSql);
        Map<String, List<Statement>> sourceGroupStatementByTable = groupStatementByTable(sourceSchemaSqlStatements);
        Map<String, List<Statement>> targetGroupStatementByTable = groupStatementByTable(targetSchemaSqlStatements);

        // 按照表依次解析
        Map<String, List<String>> result = new HashMap<>();
        Set<String> tableSet = new HashSet<>(sourceGroupStatementByTable.keySet());
        tableSet.addAll(targetGroupStatementByTable.keySet());
        for (String fullTableName : tableSet) {
            List<Statement> sourceTableStatements = sourceGroupStatementByTable.get(fullTableName);
            List<Statement> targetTableStatements = targetGroupStatementByTable.get(fullTableName);
            List<String> maintenanceList =
                maintenance(fullTableName, sourceTableStatements, targetTableStatements, operations);
            if (CommonUtils.isNotEmpty(maintenanceList)) {
                result.put(fullTableName, maintenanceList);
            }
        }

        return result;

    }

    /**
     * (只针对单表)对比两个 SQL schemas 脚本生成从 sourceSchemaSql 变化到 targetSchemaSql 需要执行 SchemaSQL 脚本。
     *
     * @param fullTableName 表格名称
     * @param sourceStatements 源 Schema SQL 脚本
     * @param targetStatements 目标 Schema SQL 脚本
     * @param operations 生成的 维护 Schema SQL 脚本 允许出现的 ddl 类型
     * @return 需要执行的 SQL 脚本列表
     */
    protected abstract List<String> maintenance(String fullTableName, List<Statement> sourceStatements,
        List<Statement> targetStatements, Set<DdlOperation> operations);

    /**
     * 解析 sql 为 statement
     * 
     * @param schemaSql 脚本
     * @return 解析后的 SQL 脚本
     */
    protected List<Statement> parseStatements(String schemaSql) {
        try {
            if (CommonUtils.isBlank(schemaSql)) {
                return Collections.emptyList();
            }
            Statements statements = CCJSqlParserUtil.parseStatements(schemaSql);
            return statements == null || CommonUtils.isEmpty(statements.getStatements()) ? Collections.emptyList()
                : statements.getStatements();
        } catch (Exception e) {
            throw new AutoDdlException("脚本解析异常", e);
        }
    }

    /**
     * 根据 表格名称分组 schema 语句
     * 
     * @param statements schema 语句
     * @return 分组后 schema 语句
     */
    protected Map<String, List<Statement>> groupStatementByTable(List<Statement> statements) {
        if (CommonUtils.isEmpty(statements)) {
            return Collections.emptyMap();
        }
        // 分组
        Map<String, List<Statement>> groupStatementByTable = new HashMap<>();
        for (Statement statement : statements) {
            Table table = null;
            if (statement instanceof CreateTable createTable) {
                table = createTable.getTable();
            } else if (statement instanceof CreateIndex createIndex) {
                table = createIndex.getTable();
            }
            if (table != null) {
                String fullTableName = getFullTableName(table.getSchemaName(), table.getName(), true);
                List<Statement> tableStatements =
                    groupStatementByTable.computeIfAbsent(fullTableName, fullTableName1 -> new LinkedList<>());
                tableStatements.add(statement);
            }
        }
        return groupStatementByTable;
    }

    /**
     * 获取表格 全名称 包括 schema name
     * 
     * @param schemaName schemaName
     * @param tableName tableName
     * @param forceAddIdentifier 是否强制处理关键字
     * @return 表格名称
     */
    public String getFullTableName(String schemaName, String tableName, boolean forceAddIdentifier) {
        if (CommonUtils.isNotBlank(schemaName)) {
            return identifier(schemaName, forceAddIdentifier) + "." + identifier(tableName, forceAddIdentifier);
        }
        return identifier(tableName, forceAddIdentifier);
    }

    /**
     * 处理关键字 关键字开始跟结束加上处理字符例如 null `null`
     * 
     * @param value 待处理值
     * @param force 是否强制处理，不是关键字也添加处理字符
     * @return 处理后结果
     */
    public String identifier(String value, boolean force) {
        if (CommonUtils.isBlank(value)) {
            return value;
        }
        if (!isKeyword(value) && !force) {
            // 不是关键字 ，不强制添加时直接返回
            return value;
        }
        value = value.trim();
        if (value.startsWith(identifier) && value.endsWith(identifier)) {
            return value;
        }
        if (value.contains(identifier)) {
            return identifier + value.replace(identifier, "\\" + identifier) + identifier;
        }
        return identifier + value + identifier;
    }

    /**
     * 是否为关键字
     * 
     * @param keyword 关键字
     * @return 是否为关键字
     */
    public boolean isKeyword(String keyword) {
        if (CommonUtils.isBlank(keyword)) {
            return false;
        }
        return keywordHub.getOrDefault(keyword.trim().toUpperCase(), false);
    }

    /**
     * 初始化 关键字
     */
    protected void initKeyword() {
        // 读取 resource 下的 关键字文件 keyword-mysql-8.txt 一个关键字占一行
        try (BufferedReader reader =
            new BufferedReader(new InputStreamReader(Objects.requireNonNull(AbstractDialect.class.getClassLoader()
                .getResourceAsStream(String.format("keyword-%s-%s.txt", getProductName(), getProductVersion())))))) {
            String keyword;
            while ((keyword = reader.readLine()) != null) {
                if (CommonUtils.isNotBlank(keyword)) {
                    keywordHub.put(keyword.trim().toUpperCase(), true);
                }
            }
        } catch (IOException e) {
            throw new AutoDdlException(" Keyword Reader Error!", e);
        }
    }

}
